// ----数组----
GLOBL ·num(SB),NOPTR,$16

DATA ·num+0(SB)/8,$0
DATA ·num+8(SB)/8,$0

// ----bool----
// bool类型的内存大小为1个字节。并且汇编中定义的变量需要手工指定初始化值，否则将可能导致产生未初始化的变量。
// 当需要将1个字节的bool类型变量加载到8字节的寄存器时，需要使用MOVBQZX指令将不足的高位用0填充。

GLOBL ·boolValue(SB),NOPTR,$1 // 未初始化

GLOBL ·trueValue(SB),NOPTR,$1 // var trueValue = true
DATA ·trueValue+0(SB)/1,$1 // 非 0 均为 true

GLOBL ·falseValue(SB),NOPTR,$1 // var falseValue = true
DATA ·falseValue+0(SB)/1,$0

// ----int----
// 汇编定义变量时初始化数据并不区分整数是否有符号。只有在CPU指令处理该寄存器数据时，
// 才会根据指令的类型来区分数据的类型或者是否带有符号位。
GOLBL ·int32Value(SB),NOPTR,$4
DATA ·int32Value+0(SB)/1,$0x01 // 第0字节
DATA ·int32Value+1(SB)/1,$0x02 // 第1字节
DATA ·int32Value+3(SB)/1,$0x03 // 第3-4字节

GOLBL ·uint32Value(SB),NOPTR,$4
DATA ·uint32Value+0(SB)/4,$0x01020304 // 第1-4字节

// float
// Go汇编语言通常无法区分变量是否是浮点数类型，与之相关的浮点数机器指令会将变量当作浮点数处理。
// Go语言的浮点数遵循IEEE754标准，有float32单精度浮点数和float64双精度浮点数之分。
// IEEE754标准中，最高位1bit为符号位，然后是指数位（指数为采用移码格式表示），然后是有效数部分（其中小数点左边的一个bit位被省略）。
// 下图是IEEE754中float32类型浮点数的bit布局：
// IEEE754浮点数还有一些奇妙的特性：比如有正负两个0；除了无穷大和无穷小Inf还有非数NaN；
// 同时如果两个浮点数有序那么对应的有符号整数也是有序的（反之则不一定成立，因为浮点数中存在的非数是不可排序的）。
// 浮点数是程序中最难琢磨的角落，因为程序中很多手写的浮点数字面值常量根本无法精确表达，浮点数计算涉及到的误差舍入方式可能也的随机的。
GLOBL ·float32Value(SB),NOPTR,$4
DATA ·float32Value+0(SB)/4,$1.5 // var float32Value = 1.5

// 我们在上一节精简的算术指令中都是针对整数，如果要通过整数指令处理浮点数的加减法必须根据浮点数的运算规则进行：
// 先对齐小数点，然后进行整数加减法，最后再对结果进行归一化并处理精度舍入问题。
// 不过在目前的主流CPU中，都提针对浮点数提供了专有的计算指令。
GLOBL ·float64Value(SB),NOPTR,$8
DATA ·float64Value+0(SB)/8,$0x01020304 // bit 方式初始化

// ----string----
// string 结构
// type reflect.StringHeader struct {
//       Data uintptr
//       Len  int
//   }
GLOBL ·helloworld2(SB),NOPTR,$16

// 同时我们可以为字符串准备真正的数据。在下面的汇编代码中，我们定义了一个text当前文件内的私有变量（以<>为后缀名）
// 虽然text<>私有变量表示的字符串只有12个字符长度，但是我们依然需要将变量的长度扩展为2的指数倍数
GLOBL ·text<>(SB),NOPTR,$16
DATA ·text<>+0(SB)/8,$"hello wo"
DATA ·text<>+8(SB)/8,$"rld!"

DATA ·helloworld2+0(SB)/8,$text<>(SB) // StringHeader.Data
DATA ·helloworld2+8(SB)/8,$12 // StringHeader.Len

// ----slice----
// slice 结构
// type reflect.SliceHeader struct {
//       Data uintptr
//       Len  int
//       Cap  int
//   }
GLOBL ·helloworld3(SB),$24 // var helloworld []byte("Hello World!")
DATA ·helloworld3+0(SB)/8,$text<>(SB) // StringHeader.Data
DATA ·helloworld3+8(SB)/8,$12  // StringHeader.Len
DATA ·helloworld3+16(SB)/8,$16 // StringHeader.Cap


// ----map/channel----
// map/channel等类型并没有公开的内部结构，它们只是一种未知类型的指针，无法直接初始化
// 在汇编代码中我们只能为类似变量定义并进行0值初始化

GLOBL ·m(SB),$8 // var m map[string]int
DATA ·m+0(SB)/8,$0

GLOBL ·ch(SB),$8 // var ch chan int
DATA ·ch+0(SB)/8,$0
// 其实在runtime包中为汇编提供了一些辅助函数。
// 比如在汇编中可以通过runtime.makemap和runtime.makechan内部函数来创建map和chan变量。辅助函数的签名如下：
// func makemap(mapType *byte, hint int, mapbuf *any) (hmap map[any]any)
// func makechan(chanType *byte, size int) (hchan chan any)
// 需要注意的是，makemap是一种范型函数，可以创建不同类型的map，map的具体类型是通过mapType参数指定。


// ----内存布局----



// ----标识符规则和特殊标志----
// 此外，Go汇编语言还在"textflag.h"文件定义了一些标志。其中用于变量的标志有DUPOK、RODATA和NOPTR几个。
// DUPOK表示该变量对应的标识符可能有多个，在链接时只选择其中一个即可（一般用于合并相同的常量字符串，减少重复数据占用的空间）。
// RODATA标志表示将变量定义在只读内存段，因此后续任何对此变量的修改操作将导致异常（recover也无法捕获）。
// NOPTR则表示此变量的内部不含指针数据，让垃圾回收器忽略对该变量的扫描。
// 如果变量已经在Go代码中声明过的话，Go编译器会自动分析出该变量是否包含指针，这种时候可以不用手写NOPTR标志。
GLOBL ·const_id(SB),NOPTR|RODAta,$8 // 多个标志之间采用竖杠分割
DATA ·const_id+0(SB)/8,$9527

